package com.yunhe.option.service.data.impl;

import com.yunhe.common.model.PageParam;
import com.yunhe.option.domain.data.Price;
import com.yunhe.option.domain.data.pk.PricePK;
import com.yunhe.common.exception.ArgumentErrorException;
import com.yunhe.common.exception.UniqueConstraintsException;
import com.yunhe.option.repository.data.PriceRepository;
import com.yunhe.option.service.data.PriceService;
import com.yunhe.common.util.QueryUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 价格Service
 * @desc 价格服务类实现
 * @author liuronglei
 */
@Service
public class PriceServiceImpl implements PriceService {
    @Autowired
    PriceRepository priceRepository;

    private void processBeforeSave(Price price) throws UniqueConstraintsException {
        PricePK pk = getPricePK(price.getAreaCode(), price.getTypeCode(), price.getDtime());
        Price newPrice = findByPk(pk);
        if (newPrice != null) {
            throw new UniqueConstraintsException("价格不唯一");
        }
    }

    /**
     * 新增价格对象
     * @param price 价格对象
     */
    @Override
    @Transactional
    public Price createPrice(Price price)
            throws ArgumentErrorException, UniqueConstraintsException {
        Price newPrice = priceRepository.save(price);
        return newPrice;
    }

    /**
     * 批量新增价格对象
     * @param priceList 价格对象列表
     */
    @Override
    @Transactional
    public Iterable<Price> createPrice(List<Price> priceList)
            throws ArgumentErrorException, UniqueConstraintsException {
        Iterable<Price> newPrices = priceRepository.saveAll(priceList);
        return newPrices;
    }

    /**
     * 根据地区代码和类型ID删除价格对象
     * @param areaCode 地区代码
     * @param typeCode 类型代码
     */
    @Override
    @Transactional
    public Long deletePriceByAreaCodeAndTypeCode(Long areaCode, Long typeCode) {
        return priceRepository.deletePriceByAreaCodeAndTypeCode(areaCode, typeCode);
    }

    /**
     * 根据时间范围更新价格
     * @param areaCode 地区代码
     * @param typeCode 类型代码
     * @param dtime 时间范围
     * @param val 值
     */
    @Override
    @Transactional
    public List<Price> updatePrices(Long areaCode, Long typeCode, List<Date> dtime, Double val) {
        Specification<Price> spec = getSpecification(areaCode, typeCode, dtime);
        Iterable<Price> priceLists = priceRepository.findAll(spec);
        Date time = dtime.get(0);
        List<Price> priceList = new ArrayList<>();
        for (Price price : priceLists) {
            price.setVal(val);
            price.setDtime(time);
            priceList.add(price);
            time = new Date(time.getTime() + 15 * 60 * 1000);
        }
        return priceRepository.saveAll(priceList);
    }

    /**
     * 更新价格
     * @param areaCode 地区代码
     * @param typeCode 类型代码
     * @param dtime 时间范围
     * @param val 值
     */
    @Override
    @Transactional
    public Price updatePrice(Long areaCode, Long typeCode, Date dtime, Double val)
            throws UniqueConstraintsException, ArgumentErrorException {
        PricePK pk = getPricePK(areaCode, typeCode, dtime);
        Price priceByPK = findByPk(pk);
        if (priceByPK == null) {
            Price price = new Price();
            price.setAreaCode(areaCode);
            price.setTypeCode(typeCode);
            price.setDtime(dtime);
            price.setVal(val);
            return createPrice(price);
        } else {
            priceByPK.setVal(val);
            return priceRepository.save(priceByPK);
        }
    }

    /**
     * 根据条件获得价格对象列表
     * @param areaCode 地区代码
     * @param typeCode 类型代码
     * @param dtime 时间
     * @param pageParam 分页对象
     */
    @Override
    public Iterable<Price> findPrices(Long areaCode, Long typeCode, List<Date> dtime, PageParam pageParam) {
        Specification spec = (Specification<Price>) (root, query, cb) -> {
            List<Predicate> criteria = new ArrayList<>();
            if (areaCode != null) {
                Path<String> areaCodePath = root.get("areaCode");
                criteria.add(cb.equal(areaCodePath, areaCode));
            }
            if (typeCode != null) {
                Path<Long> typeCodePath = root.get("typeCode");
                criteria.add(cb.equal(typeCodePath, typeCode));
            }
            if (dtime != null && dtime.size() > 0) {
                Path<Date> dtimePath = root.get("dtime");
                if (dtime.size() == 1) {
                    criteria.add(cb.equal(dtimePath, dtime.get(0)));
                } else if (dtime.size() == 2) {
                    criteria.add(cb.between(dtimePath, dtime.get(0), dtime.get(1)));
                }
            }
            if (criteria.size() == 1) {
                query.where(criteria.get(0));
            } else {
                query.where(criteria.toArray(new Predicate[0]));
            }
            return null;
        };
        if (QueryUtil.needPaging(pageParam)) {
            return priceRepository.findAll(spec, QueryUtil.getPageRequest(pageParam));
        } else {
            Sort sort = pageParam == null ? null : QueryUtil.getSort(pageParam.getSort());
            if (sort != null) {
                return priceRepository.findAll(spec, sort);
            } else {
                return priceRepository.findAll(spec);
            }
        }
    }

    /**
     * 根据ID获得价格对象
     * @param pk pk
     */
    private Price findByPk(PricePK pk) {
        return priceRepository.findById(pk).orElse(null);
    }

    /**
     * 获得查询参数
     * @param areaCode 地区代码
     * @param typeCode 类型代码
     * @param dtime 时间范围
     */
    private Specification getSpecification(Long areaCode, Long typeCode, List<Date> dtime) {
        Specification spec = (root, query, cb) -> {
            List<Predicate> criteria = new ArrayList<>();
            if (areaCode != null) {
                Path<Long> areaCodePath = root.get("areaCode");
                criteria.add(cb.equal(areaCodePath, areaCode));
            }
            if (typeCode != null) {
                Path<Long> typeCodePath = root.get("typeCode");
                criteria.add(cb.equal(typeCodePath, typeCode));
            }
            if (dtime != null && dtime.size() > 0) {
                Path<Date> dtimePath = root.get("dtime");
                if (dtime.size() == 1) {
                    criteria.add(cb.equal(dtimePath, dtime.get(0)));
                } else if (dtime.size() == 2) {
                    criteria.add(cb.between(dtimePath, dtime.get(0), dtime.get(1)));
                }
            }
            if (criteria.size() == 1) {
                query.where(criteria.get(0));
            } else {
                query.where(criteria.toArray(new Predicate[0]));
            }
            return null;
        };
        return spec;
    }

    private PricePK getPricePK(Long areaCode, Long typeCode, Date dtime){
        PricePK pk = new PricePK();
        pk.setAreaCode(areaCode);
        pk.setTypeCode(typeCode);
        pk.setDtime(dtime);
        return pk;
    }
}
